#ifndef _UrlQueues_H_
#define _UrlQueues_H_
#include <list>
#include <regex.h>
#include <sys/types.h>
#include "url.hpp"
#include "Webcrawler.hpp"
#include "Log.hpp"

#include "Plugin.hpp"


class UrlFilter;
// 统一资源定位符队列
class UrlQueues 
{
    UrlFilter& _filter;//统一资源定位符过滤器
	std::list<RawUrl> _rawUrlQueue;//原始统一资源定位符队列
	mutable pthread_mutex_t _rawUrlMutex;//原始统一资源定位符队列互斥锁
	pthread_cond_t _rawUrlNoFull;//原始统一资源定位符队列非满条件变量
	pthread_cond_t _rawUrlNoEmpty;//原始统一资源定位符队列非空条件变量
	std::list<DnsUrl> _dnsUrlQueue;//解析统一资源定位符队列
	mutable pthread_mutex_t _dnsUrlMutex;//解析统一资源定位符队列互斥锁	
	pthread_cond_t _dnsUrlNoFull;//解析统一资源定位符队列非满条件变量
	pthread_cond_t _dnsUrlNoEmpty;//解析统一资源定位符队列非空条件变量
public:
    //[in] 统一资源定位符过滤
	UrlQueues (UrlFilter& filter) :_filter(filter) // 初始化统一资源定位符过滤器
    { 
        // 初始化原始统一资源定位符队列互斥锁
        pthread_mutex_init(&_rawUrlMutex, NULL);
        // 初始化原始统一资源定位符队列非满条件变量
        pthread_cond_init(&_rawUrlNoFull, NULL);
        // 初始化原始统一资源定位符队列非空条件变量
        pthread_cond_init(&_rawUrlNoEmpty, NULL);
        // 初始化解析统一资源定位符队列互斥锁
        pthread_mutex_init(&_dnsUrlMutex, NULL);
        // 初始化解析统一资源定位符队列非满条件变量
        pthread_cond_init(&_dnsUrlNoFull, NULL);
        // 初始化解析统一资源定位符队列非空条件变量
        pthread_cond_init(&_dnsUrlNoEmpty, NULL);
    }
	~UrlQueues ()
    {
        // 销毁解析统一资源定位符队列非空条件变量
        pthread_cond_destroy (&_dnsUrlNoEmpty);
        // 销毁解析统一资源定位符队列非满条件变量
        pthread_cond_destroy (&_dnsUrlNoFull);
        // 销毁解析统一资源定位符队列互斥锁
        pthread_mutex_destroy (&_dnsUrlMutex);
        // 销毁原始统一资源定位符队列非空条件变量
        pthread_cond_destroy (&_rawUrlNoEmpty);
        // 销毁原始统一资源定位符队列非满条件变量
        pthread_cond_destroy (&_rawUrlNoFull);
        // 销毁原始统一资源定位符队列互斥锁
        pthread_mutex_destroy (&_rawUrlMutex);

    }
	//压入原始统一资源定位符
    //< [in] 原始统一资源定位符
	void pushRawUrl (const RawUrl& rawUrl)
    {
        // 加锁原始统一资源定位符队列互斥锁
        pthread_mutex_lock(&_rawUrlMutex);
        // 若已被处理过
        if (_filter.exist(rawUrl._strUrl))
        {  
            lg(Debug, "bind error, %s: %d", strerror(errno), errno);
        }
        // 否则，调用统一资源定位符插件处理函数，若成功
        else if (g_app->_pluginMngr.invokeUrlPlugins(const_cast<RawUrl*>(&rawUrl)))
        {
            // 若配置器中的原始统一资源定位符队列最大容量有效且到限
            while (0 <= g_app->_cfg._maxRawUrls &&
                   (size_t)g_app->_cfg._maxRawUrls <= _rawUrlQueue.size())
                // 等待原始统一资源定位符队列非满条件变量
                pthread_cond_wait(&_rawUrlNoFull, &_rawUrlMutex);

            // 向原始统一资源定位符队列压入原始统一资源定位符
            _rawUrlQueue.push_back(rawUrl);

            // 记录调试日志
            // g_app->m_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__,
            //                     "原始统一资源定位符\"%s\"入队", rawUrl.m_strUrl.c_str());

            // 若原始统一资源定位符队列由空变为非空
            if (_rawUrlQueue.size() == 1)
                // 唤醒等待原始统一资源定位符队列非空条件变量的线程
                pthread_cond_signal(&_rawUrlNoEmpty);
        }

        // 解锁原始统一资源定位符队列互斥锁
        pthread_mutex_unlock(&_rawUrlMutex);
    }
	/// @brief 弹出原始统一资源定位符
	/// @return 原始统一资源定位符
	RawUrl popRawUrl ()
    {
        // 加锁原始统一资源定位符队列互斥锁
        pthread_mutex_lock(&_rawUrlMutex);
        // 若原始统一资源定位符队列空 等待原始统一资源定位符队列非空条件变量
        while (_rawUrlQueue.empty())
            pthread_cond_wait(&_rawUrlNoEmpty, &_rawUrlMutex);

        // 获取原始统一资源定位符队列首元素
        RawUrl rawUrl = _rawUrlQueue.front();
        // 从原始统一资源定位符队列弹出原始统一资源定位符
        _rawUrlQueue.pop_front();

        // 记录调试日志
        // g_app->m_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__,
        //                     "原始统一资源定位符\"%s\"出队", rawUrl.m_strUrl.c_str());

        // 若原始统一资源定位符队列由满变为非满 // 唤醒等待原始统一资源定位符队列非满条件变量的线程
        if (_rawUrlQueue.size() == (size_t)g_app->_cfg._maxRawUrls - 1)
            pthread_cond_signal(&_rawUrlNoFull);
        // 解锁原始统一资源定位符队列互斥锁
        pthread_mutex_unlock(&_rawUrlMutex);
        // 返回原始统一资源定位符
        return rawUrl;
    }

    /// @brief 获取原始统一资源定位符数
	/// @return 原始统一资源定位符数
	size_t sizeRawUrl () const
    {
        // 加锁原始统一资源定位符队列互斥锁
        pthread_mutex_lock(&_rawUrlMutex);

        // 获取原始统一资源定位符队列大小
        size_t size = _rawUrlQueue.size();

        // 解锁原始统一资源定位符队列互斥锁
        pthread_mutex_unlock(&_rawUrlMutex);
        // 返回原始统一资源定位符数
        return size;
    }

    /// @brief 原始统一资源定位符队列空否
	/// @retval true  空
	/// @retval false 不空
	bool emptyRawUrl () const
    {
        // 加锁原始统一资源定位符队列互斥锁
        pthread_mutex_lock (&_rawUrlMutex);
        // 获取原始统一资源定位符队列是否空
        bool empty = _rawUrlQueue.empty ();
        // 解锁原始统一资源定位符队列互斥锁
        pthread_mutex_unlock (&_rawUrlMutex);
        // 返回原始统一资源定位符队列是否空
        return empty;
    }

	// 原始统一资源定位符队列满否 true 满
	bool fullRawUrl (void) const
    {
        // 加锁原始统一资源定位符队列互斥锁
        pthread_mutex_lock (&_rawUrlMutex);

        // 获取原始统一资源定位符队列是否满
        bool full = 0 <= g_app->_cfg._maxRawUrls &&
            (size_t)g_app->_cfg._maxRawUrls <= _rawUrlQueue.size ();

        // 解锁原始统一资源定位符队列互斥锁
        pthread_mutex_unlock (&_rawUrlMutex);
        // 返回原始统一资源定位符队列是否满
        return full;
    }

	//清空原始统一资源定位符队列
	void clearRawUrl (void)
    {
        // 加锁原始统一资源定位符队列互斥锁
        pthread_mutex_lock (&_rawUrlMutex);

        // 清空原始统一资源定位符队列
        _rawUrlQueue.clear ();
        // 唤醒等待原始统一资源定位符队列非满条件变量的线程
        pthread_cond_signal (&_rawUrlNoFull);

        // 解锁原始统一资源定位符队列互斥锁
        pthread_mutex_unlock (&_rawUrlMutex);
    }


	//压入解析统一资源定位符 //[in] 解析统一资源定位符)
	void pushDnsUrl (DnsUrl const& dnsUrl)
    {
        // 加锁解析统一资源定位符队列互斥锁
        pthread_mutex_lock(&_dnsUrlMutex);

        // 若配置器中的解析统一资源定位符队列最大容量有效且到限 等待解析统一资源定位符队列非满条件变量
        while (0 <= g_app->_cfg._maxDnsUrls && (size_t)g_app->_cfg._maxDnsUrls <= _dnsUrlQueue.size())
            pthread_cond_wait(&_dnsUrlNoFull, &_dnsUrlMutex);

        // 向解析统一资源定位符队列压入解析统一资源定位符
        _dnsUrlQueue.push_back(dnsUrl);

        // 记录调试日志
        // g_app->m_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__,
        //                     "解析统一资源定位符\"ip=%s, port=%d, path=%s\"入队",
        //                     dnsUrl.m_ip.c_str(), dnsUrl.m_port, dnsUrl.m_path.c_str());

        // 若解析统一资源定位符队列由空变为非空 // 唤醒等待解析统一资源定位符队列非空条件变量的线程
        if (_dnsUrlQueue.size() == 1) pthread_cond_signal(&_dnsUrlNoEmpty);
        // 解锁解析统一资源定位符队列互斥锁
        pthread_mutex_unlock(&_dnsUrlMutex);
    }

    //弹出解析统一资源定位符
	//return 解析统一资源定位符
    DnsUrl popDnsUrl()
    {
        // 加锁解析统一资源定位符队列互斥锁
        pthread_mutex_lock(&_dnsUrlMutex);
        // 若解析统一资源定位符队列空 // 等待解析统一资源定位符队列非空条件变量
        while (_dnsUrlQueue.empty())
            pthread_cond_wait(&_dnsUrlNoEmpty, &_dnsUrlMutex);

        // 获取解析统一资源定位符队列首元素
        DnsUrl dnsUrl = _dnsUrlQueue.front();
        // 从解析统一资源定位符队列弹出解析统一资源定位符
        _dnsUrlQueue.pop_front();

        // 记录调试日志
        // g_app->m_log.printf(Log::LEVEL_DBG, __FILE__, __LINE__,
        //                     "解析统一资源定位符\"ip=%s, port=%d, path=%s\"出队",
        //                     dnsUrl.m_ip.c_str(), dnsUrl.m_port, dnsUrl.m_path.c_str());

        // 若解析统一资源定位符队列由满变为非满,唤醒等待解析统一资源定位符队列非满条件变量的线程
        if (_dnsUrlQueue.size() == (size_t)g_app->_cfg._maxDnsUrls - 1)
            pthread_cond_signal(&_dnsUrlNoFull);

        // 解锁解析统一资源定位符队列互斥锁
        pthread_mutex_unlock(&_dnsUrlMutex);
        // 返回解析统一资源定位符
        return dnsUrl;
    }

    /// @brief 获取解析统一资源定位符数
	/// @return 解析统一资源定位符数
	size_t sizeDnsUrl () const
    {
        // 加锁解析统一资源定位符队列互斥锁
        pthread_mutex_lock(&_dnsUrlMutex);
        // 获取解析统一资源定位符队列大小
        size_t size = _dnsUrlQueue.size();
        // 解锁解析统一资源定位符队列互斥锁
        pthread_mutex_unlock(&_dnsUrlMutex);
        // 返回解析统一资源定位符数
        return size;
    }

    //解析统一资源定位符队列空否
	//true  空
	//false 不空
	bool emptyDnsUrl (void) const
    {
        // 加锁解析统一资源定位符队列互斥锁
        pthread_mutex_lock(&_dnsUrlMutex);
        // 获取解析统一资源定位符队列是否空
        bool empty = _dnsUrlQueue.empty();
        // 解锁解析统一资源定位符队列互斥锁
        pthread_mutex_unlock(&_dnsUrlMutex);
        // 返回解析统一资源定位符队列是否空
        return empty;
    }

    /// @brief 解析统一资源定位符队列满否
	/// @retval true  满
	/// @retval false 不满
    bool fullDnsUrl() const
    {
        // 加锁解析统一资源定位符队列互斥锁
        pthread_mutex_lock(&_dnsUrlMutex);

        // 获取解析统一资源定位符队列是否满
        bool full = 0 <= g_app->_cfg._maxDnsUrls &&
                    (size_t)g_app->_cfg._maxDnsUrls <= _dnsUrlQueue.size();

        // 解锁解析统一资源定位符队列互斥锁
        pthread_mutex_unlock(&_dnsUrlMutex);
        // 返回解析统一资源定位符队列是否满
        return full;
    }

    /// @brief 清空解析统一资源定位符队列
    void clearDnsUrl(void)
    {
        // 加锁解析统一资源定位符队列互斥锁
        pthread_mutex_lock(&_dnsUrlMutex);

        // 清空解析统一资源定位符队列
        _dnsUrlQueue.clear();
        // 唤醒等待解析统一资源定位符队列非满条件变量的线程
        pthread_cond_signal(&_dnsUrlNoFull);

        // 解锁解析统一资源定位符队列互斥锁
        pthread_mutex_unlock(&_dnsUrlMutex);
    }

    //从超文本标记语言页面内容中抽取统一资源定位符
    //[in] 超文本标记语言页面内容字符串
    //[in] 被抽取页面解析统一资源定位符
	void extractUrl (char const*   html, DnsUrl const& dnsUrl)
    {
        // 正则表达式
        regex_t ex;

        // 编译正则表达式：href="\s*\([^ >"]*\)\s*"
        //     \s - 匹配任意空白字符(空格、制表、换页等)
        //      * - 重复前一个匹配项任意次
        //     \( - 子表达式左边界
        //     \) - 子表达式右边界
        // [^ >"] - 匹配任意不是空格、大于号和双引号的字符
        int error = regcomp(&ex, "href=\"\\s*\\([^ >\"]*\\)\\s*\"", 0);
        // 若失败
        if (error)
        {
            // 错误信息缓冲区
            char errInfo[1024];
            // 获取正则表达式编译错误信息
            regerror(error, &ex, errInfo, sizeof(errInfo) / sizeof(errInfo[0]));
            // 记录一般错误日志
            // g_app->m_log.printf(Log::LEVEL_ERR, __FILE__, __LINE__,
            //                     "regcomp: %s", errInfo);
        }

        // 匹配集合
        regmatch_t match[2];

        // 在超文本标记语言页面内容字符串中，
        // 查找所有与正则表达式匹配的内容
        while (regexec(&ex, html, sizeof(match) / sizeof(match[0]), match, 0) != REG_NOMATCH)
        {
            // regex : href="\s*\([^ >"]*\)\s*"
            // html  : ...href="  /software/download.html  "...
            //            |       |<-----match[1]------>|  |
            //            |     rm_so                 rm_eo|
            //            |<-------------match[0]--------->|
            //          rm_so                            rm_eo

            // 匹配子表达式的内容首地址
            html += match[1].rm_so;
            // 匹配子表达式的内容字符数
            size_t len = match[1].rm_eo - match[1].rm_so;
            // 匹配子表达式的内容字符串，即超链接中的统一资源定位符
            std::string strUrl(html, len);
            // 移至匹配主表达式的内容后，以备在下一轮循环中继续查找
            html += len + match[0].rm_eo - match[1].rm_eo;

            // 若是二进制资源,继续下一次循环
            if (isBinary(strUrl))
                continue;
            // 若添加域名失败
            if (!dnsUrl.attachDomain(strUrl))
                continue;

            // 记录调试日志
            // g_app->m_log.printf (Log::LEVEL_DBG, __FILE__, __LINE__,
            // 	"抽取到一个深度为%d的统一资源定位符\"%s\"",
            // 	dnsUrl._depth + 1, strUrl.c_str ());

            // 若规格化失败
            if (!RawUrl::normalized(strUrl))
            {
                // 记录警告日志
                // g_app->m_log.printf (Log::LEVEL_WAR, __FILE__, __LINE__,
                // 	"规格化统一资源定位符\"%s\"失败", strUrl.c_str ());
                // 继续下一轮循环
                continue;
            }

            // 压入原始统一资源定位符队列
            pushRawUrl(RawUrl(strUrl, RawUrl::ETYPE_HTML, dnsUrl._depth + 1));
        }

        // 释放正则表达式
        regfree(&ex);
    }

private:
	//判断某统一资源定位符所表示的资源是否是二进制资源
	//true  是二进制资源
	//false 非二进制资源
    ///< [in] 统一资源定位符字符串
	static bool isBinary (std::string const& strUrl )
    {
        // 统一资源定位符字符串中'.'字符的下标
        std::string::size_type pos;
        // 若统一资源定位符字符串中最后一个'.'字符，连同其后的子串
        // 构成二进制资源的文件扩展名，则返回true，否则返回false
        return (pos = strUrl.find_last_of('.')) != std::string::npos &&
               std::string(".jpg.jpeg.gif.png.ico.bmp.swf").find(strUrl.substr(pos)) != std::string::npos;
    }
};

#endif //_UrlQueues_H_
